#!python3.2
#-*- coding: utf-8 -*-
"""
transbuild.py -- automatic Karrigell translation.ini builder

transbuild does the following:
  1. Extracts phrases from _() and _[] definitions in python scripts 
     and KT templates
  2. Uses Bing or Google translation services to translate the phrases
  3. Writes a Karrigel translation.ini file.
  4. Can optionally update an existing translation.ini file
     (only phrases that are not already defined in the existing 
     ini file will be added.)
"""
epilog = """
Examples:
  
  1. Build a new translation.ini file from scratch, extracting phrases
     from .py and .kt files in myappdir and sub-directories:
     
  >  transbuild -r -o translations.ini myappdir
  
  2. Overwrite existing ini file
     
  >  transbuild -rf -o translations.ini myappdir
  
  3. Create a new ini file, just adding entries not found in an exisiting
     ini file:
     
  >  transbuild -r -i translations.ini -o translations2.ini myappdir
  
  4. As above but overwriting exsting ini file:
     
  >  transbuild -rf -i translations.ini -o translations.ini myappdir
  
  5. Specify languages to translate to:
     
  >  transbuild -rf -l "en,fr,de,ja" -o translations.ini myappdir
  
Application IDs/Keys

transbuild uses the Bing or Google translation web services. These services
require an app ID or key which can be obtained by registering with Bing or Google.

The keys/IDs are hard coded into the script. Edit the values of the following 
variables in the script:

GOOGLE_APP_ID = 'xxxxxxxxxxx'
BING_APP_ID = 'xxxxxxxxxxx' 

Registration pages:

Bing: http://www.bing.com/developers
Google: https://code.google.com/apis/console/?pli=1#project:1067976174128

Note the announcement from Google below:
Google Translate API v2 is now available as a paid service. The courtesy limit
for existing Translate API v2 projects created prior to August 24, 2011 will be 
reduced to zero on December 1, 2011. In addition, the number of requests your
application can make per day will be limited.

"""

# Set the app ids (keys) for the translation services
GOOGLE_APP_ID = 'AIzaSyAPc_-loqfyXALPxfSNtW0W6-QYglnI3Jg'
BING_APP_ID = '586AEA9498F3CC72E37B88E39D79FD433B671BF8'

import sys, os
import glob
import re
import configparser
import argparse
import pprint

#Process command line
parser = argparse.ArgumentParser(
    description=__doc__,
    epilog=epilog,
    formatter_class=argparse.RawDescriptionHelpFormatter
    )
parser.add_argument('path', default=None,
    help='Path to directory containing scripts and template files')
parser.add_argument('-o', '--output', required=True,default=None,
    help='Output inifile')
parser.add_argument('-i', '--input', required=False, default=None,
    help='Input inifile')
parser.add_argument('-l', '--log', required=False, default=None,
    help='Log file')
parser.add_argument('-y', '--force', required=False, action='store_true',
    help='Overwrite outout ini file if it already exists')
parser.add_argument('-r', '--recurse', required=False, action='store_true',
    help='Process sub-directories recursively')
parser.add_argument('-e', '--extensions', required=False, default='.py,.kt',
    help='File extensions to process (default: ".py,.kt")')
parser.add_argument('-f', '--fromlang', required=False, default='en',
    help='Language to translate from (default: en)')
parser.add_argument('-t', '--tolang', required=False, default=None,
    help='Languages to translate to (default: all languages supported by translation service)')
parser.add_argument('-q', '--quiet', required=False, action='store_true',
    help='Suppress logs and information messages')
parser.add_argument('-s', '--service', required=False,default='bing',
    choices=('bing','google'), help='Web translation service to use')                    
args = parser.parse_args()

# Check if output ini file already exists
if not args.force and os.path.isfile(args.output):
    raise IOError('Output file %s already exists. Use a different file name or specify -y to overwrite.' \
        % args.output)

if args.service == 'google':
    from gtrans import *
    set_app_id(GOOGLE_APP_ID)
else:
    from bingtrans import *
    set_app_id(BING_APP_ID)

# Turn pattern into a set
args.extensions = set(args.extensions.split(','))

# Open log file
if args.quiet:
    class NullFile(object): 
        def write(self,value): pass
    logf = NullFile()   
elif args.log:
    logf = open(args.log, 'w')
else:
    logf = sys.stdout
    
def load_ini(inifile,encoding='utf-8'):
    ini = configparser.ConfigParser()
    ini.read([inifile],encoding=encoding)
    translations = {}
    for section in ini.sections():
        translations[section] = {}
        for option in ini.options(section):
            translations[section][option]=ini.get(section,option)
    del ini
    return translations
    
regex = re.compile(r'''\b_(?:\[|\(['"]+)(.*?[^\\])(?:\]|['"]+\))''')
def get_strings(script, regex=regex):
    f = open(script, 'r')
    res = []
    linenum = 0
    for line in f.readlines():
        linenum += 1
        found = regex.findall(line)
        for _string in found:
            res.append((_string,script,linenum))
    f.close()
    return res

def get_translations(phrases, source='en', languages=[]):
    print(source,languages)
    translations = {}
    invalid_langs = set()
    for phrase in phrases:
        print(phrase)
        print('\t', end='')
        translations[phrase] = {}
        for lang in languages:
            if lang in invalid_langs:
                continue
            print(lang, end=' ')
            if lang == source:
                other = phrase
                # The source language phrase is the default value
                translations[phrase]['default'] = other
            else:
                try:
                    other = translate(phrase, source, lang)
                except ServiceError:
                    print('Error translating to %s' % lang)
                    invalid_langs.add(lang)
                    if invalid_langs == languages:
                        print('No valid languages to process')
                        return translations
                    continue
            translations[phrase][lang] = other
        print()
    return translations
                
            
def main():
    # If requested, load an existing ini file
    orig_translations = {}
    orig_languages = set()
    if args.input:
        orig_translations = load_ini(args.input)
        # Get the set of target languages from the input ini file,
        for p in orig_translations:
            for l in orig_translations[p]:
                orig_languages.add(l)
        # Don't include 'default', just real languages
        orig_languages.remove('default')
        
    # Extract phrases to translate from script and template files
    matches = []    
    for root, dirs, files in os.walk(args.path):
        for name in files:
            if os.path.splitext(name)[1] in args.extensions:
                abs_name = os.path.join(root,name)
                matches += get_strings(abs_name)
        if not args.recurse: break

    # Write report of phrases extracted to log file
    matches.sort()
    for key, script, linenum in matches:
        print('%s:%s:%s' % (script,linenum,key), file=logf)
    
    # Phrases to be translated
    phrases = list(set([m[0] for m in matches]))
    
    # Build list of target languages
    if args.tolang:
        languages = args.tolang.lower().split(',')
    else:
        languages = get_languages()
    if args.fromlang not in languages:
        languages.append(args.fromlang)
    
    # Check if anything needs to be done
    new_phrases = list(set(phrases) - set(orig_translations))
    if not new_phrases and orig_languages == set(languages):
        print('\nNo new phrases to translate or no new target languages')
        return
        
    # Get translations
    if orig_languages == set(languages):
        # New phrases, no new languages, just process new phrases
        phrases = new_phrases
    else:
        # New languages
        languages = list(set(languages) - orig_languages)
    phrases.sort(key=str.lower)
    languages.sort(key=str.lower)
    translations = get_translations(phrases, args.fromlang, languages)
    
    # Merge the original translations with the new translations.
    # Keep the original where a phrase has already been defined    
    for p in orig_translations:
        translations[p].update(orig_translations[p])    
    
    # Save the output ini file
    outf = open(args.output,'w',encoding='utf-8')
    phrases = list(translations.keys())
    phrases.sort(key=str.lower)    
    for phrase in phrases:
        outf.write('\n[%s]\n' % phrase)
        languages = list(translations[phrase].keys())
        languages.sort(key=str.lower)
        for lang in languages:
            line = "%s=%s\n" % (lang, translations[phrase][lang])
            outf.write(line)
    outf.close()
main()